#pragma once
#include "function_traits.h"
#include "packet.h"

namespace KRPC {

template<typename F>
static bool call(F &func, Packet &packet, 
		typename std::enable_if<!std::is_void<typename function_traits<decay_t<F>>::return_type>::value, int>::type = 0)
{
	using ArgsTuple = typename function_traits<decay_t<F>>::args_tuple;
	using ArgsTupleIndexs = typename function_traits<decay_t<F>>::args_tuple_indexs;
	ArgsTuple tp;
	if (packet.unpack(tp)) {
		packet.pack(call_function(func, tp, ArgsTupleIndexs()));
		return true;
	} else {
		return false;
	}
}

template<typename F>
static bool call(F &func, Packet &packet, 
		typename std::enable_if<std::is_void<typename function_traits<decay_t<F>>::return_type>::value, int>::type = 0)
{
	using ArgsTuple = typename function_traits<decay_t<F>>::args_tuple;
	using ArgsTupleIndexs = typename function_traits<decay_t<F>>::args_tuple_indexs;
	ArgsTuple tp;
	if (packet.unpack(tp)) {
		call_function(func, tp, ArgsTupleIndexs());
		packet.pack();
		return true;
	} else {
		return false;
	}
}

template<typename F, typename Self>
static bool call(F &func, Self* self, Packet &packet,
	typename std::enable_if<!std::is_void<typename function_traits<decay_t<F>>::return_type>::value, int>::type = 0)
{
	using ArgsTuple = typename function_traits<decay_t<F>>::args_tuple;
	using ArgsTupleIndexs = typename function_traits<decay_t<F>>::args_tuple_indexs;
	ArgsTuple tp;
	if (packet.unpack(tp)) {
		packet.pack(call_function(func, self, tp, ArgsTupleIndexs()));
		return true;
	} else {
		return false;
	}
}

template<typename F, typename Self>
static bool call(F &func, Self* self, Packet &packet,
	typename std::enable_if<std::is_void<typename function_traits<decay_t<F>>::return_type>::value, int>::type = 0)
{
	using ArgsTuple = typename function_traits<decay_t<F>>::args_tuple;
	using ArgsTupleIndexs = typename function_traits<decay_t<F>>::args_tuple_indexs;
	ArgsTuple tp;
	if (packet.unpack(tp)) {
		call_function(func, self, tp, ArgsTupleIndexs());
		packet.pack();
		return true;
	} else {
		return false;
	}
}

template<typename ... Args>
struct Caller;

template<typename F, typename Self>
struct Caller<F, Self>
{
	bool operator()(const F &func, Self *self, Packet &packet) const {return call(func, self, packet);}
	bool operator()(F &func, Self *self, Packet &packet) const {return call(func, self, packet);}
	bool operator()(F &&func, Self *self, Packet &packet) const {return call(func, self, packet);}
};

template<typename F>
struct Caller<F>
{
	bool operator()(const F &func, Packet &packet) const {return call(func, packet);}
	bool operator()(F &func, Packet &packet) const {return call(func, packet);}
	bool operator()(F &&func, Packet &packet) const {return call(func, packet);}
};

} //end of namespace KRPC
